# -*- encoding: utf-8 -*-

"""
---------------------------------------------------
@File       : life_game_v5_thread_pool_executor.py
@Author     : maixiaochai
@Email      : maixiaochai@outlook.com
@CreatedOn  : 2021/7/23 14:36
---------------------------------------------------
    [ 生命游戏线程池版 ]

    > concurrent.future.ThreadPoolExecutor结合了线程（Thread）方案与队列（Queue）方案的优势，
      可以平行地处理声明游戏的 I/O 操作

    > 优点：
         1）线程数量可控，资源消耗较少
         2）future.result()，会把任务执行中遇到的异常自动抛给调用者
    > 缺点：
        1）I/O并行能力并不高
"""
from threading import Lock
from time import sleep
from concurrent.futures import ThreadPoolExecutor

from effective_python_90.life_game_v1 import ColumnPrinter

ALIVE = '*'
EMPTY = '-'


class Grid:
    def __init__(self, height, width):
        self.height = height
        self.width = width
        self.rows = []
        for _ in range(self.height):
            self.rows.append([EMPTY] * self.width)

    def get(self, y, x):
        return self.rows[y % self.height][x % self.width]

    def set(self, y, x, state):
        self.rows[y % self.height][x % self.width] = state

    def __str__(self):
        output = ''
        for row in self.rows:
            for cell in row:
                output += cell
            output += '\n'
        return output


class LockingGrid(Grid):
    def __init__(self, height, width):
        super().__init__(height, width)
        self.lock = Lock()

    def __str__(self):
        with self.lock:
            return super().__str__()

    def get(self, y, x):
        with self.lock:
            return super().get(y, x)

    def set(self, y, x, state):
        with self.lock:
            return super().set(y, x, state)


def count_neighbors(y, x, get):
    n_ = get(y - 1, x + 0)  # North
    ne = get(y - 1, x + 1)  # Northeast
    e_ = get(y + 0, x + 1)  # East
    se = get(y + 1, x + 1)  # Southeast
    s_ = get(y + 1, x + 0)  # South
    sw = get(y + 1, x - 1)  # Southwest
    w_ = get(y + 0, x - 1)  # West
    nw = get(y - 1, x - 1)  # Northwest
    neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]
    count = 0
    for state in neighbor_states:
        if state == ALIVE:
            count += 1
    return count


def game_logic(state, neighbors):
    if state == ALIVE:
        if neighbors < 2:
            return EMPTY  # Die: Too few
        elif neighbors > 3:
            return EMPTY  # Die: Too many
    else:
        if neighbors == 3:
            return ALIVE  # Regenerate

    sleep(0.1)
    return state


def step_cell(y, x, get, set):
    state = get(y, x)
    neighbors = count_neighbors(y, x, get)
    next_state = game_logic(state, neighbors)
    set(y, x, next_state)


def simulate_pool(pool, grid):
    """
        一代推进到下一代，只推进一代
    """
    next_grid = LockingGrid(grid.height, grid.width)

    futures = []
    for y in range(grid.height):
        for x in range(grid.width):
            args = (y, x, grid.get, next_grid.set)
            future = pool.submit(step_cell, *args)  # 扇出
            futures.append(future)

    for future in futures:
        future.result()

    return next_grid


def main():
    grid = LockingGrid(5, 9)
    grid.set(0, 3, ALIVE)
    grid.set(1, 4, ALIVE)
    grid.set(2, 2, ALIVE)
    grid.set(2, 3, ALIVE)
    grid.set(2, 4, ALIVE)

    # print(grid)
    """
    out:
            0     |     1     |     2     |     3     |     4    
        ---*----- | --------- | --------- | --------- | ---------
        ----*---- | --*-*---- | ----*---- | ---*----- | ----*----
        --***---- | ---**---- | --*-*---- | ----**--- | -----*---
        --------- | ---*----- | ---**---- | ---**---- | ---***---
        --------- | --------- | --------- | --------- | ---------
    """

    columns = ColumnPrinter()
    with ThreadPoolExecutor(max_workers=50) as pool:
        for i in range(5):
            columns.append(str(grid))
            grid = simulate_pool(pool, grid)

    print(columns)


if __name__ == '__main__':
    main()
